﻿/*                       NOTICE
 * 
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-10-C-0001, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * Approved for Public Release: 11-2435
 * © 2011 The MITRE Corporation. All Rights Reserved. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */



using System;
using System.Collections.Generic;
using System.Text;
using TUIO;
using MXSE.MultiTouch.Gestures;
using Microsoft.Xna.Framework;

namespace XNAGestureRecognition
{
    /// <summary>
    /// Handles all contacts on a Microsoft Surface.
    /// Needs to have the ContactAdded, ContactChanged,
    /// and ContactRemoved methods added as EventHandlers
    /// to a ContactTarget.
    /// 
    /// This class is almost entirely Update based, instead
    /// of Event based.
    /// </summary>
    public class TUIOContactManager : GameComponent
    {
        //Lists of basic gestures are sorted by the Contact IDs
        //Properties return read only lists of gestures
        public SortedList<long, TapGesture> tapGestures;
        public SortedList<long, TouchGesture> touchGestures;
        public SortedList<long, SlideGesture> slideGestures;
        public SortedList<long, DragGesture> dragGestures;
        public SortedList<long, HoldGesture> holdGestures;
        public GestureRecognizer recognizer
        {
            get;
            set;
        }

        public IList<HoldGesture> HoldGestures
        {
            get
            {
                return holdGestures.Values;
            }
        }

        /// <summary>
        /// Read-only IList of current TapGestures
        /// </summary>
        public IList<TapGesture> TapGestures
        {
            get
            {
                return tapGestures.Values;
            }
        }
        /// <summary>
        /// Read-only IList of current TouchGestures
        /// </summary>
        public IList<TouchGesture> TouchGestures
        {
            get
            {
                return touchGestures.Values;
            }
        }
        /// <summary>
        /// Read-only IList of current SlideGestures
        /// </summary>
        public IList<SlideGesture> SlideGestures
        {
            get
            {
                return slideGestures.Values;
            }
        }
        /// <summary>
        /// Read-only IList of current HoldGestures
        /// </summary>
        public IList<DragGesture> DragGestures
        {
            get
            {
                return dragGestures.Values;
            }
        }

        //Queue of new TouchGestures to add
        private List<TouchGesture> newTouches;
        private List<TapGesture> newTaps;

        //Unsorted list of ComplexGestures, which have references
        // to BasicGestures.
        private List<ComplexGesture> complexGestures;
        private List<ComplexGesture> newComplex;

        /// <summary>
        /// Read-only list of current ComplexGestures
        /// </summary>
        public IList<ComplexGesture> ComplexGestures
        {
            get
            {
                return complexGestures.AsReadOnly();
            }
        }
       
        public TUIOContactManager(Game game):base(game)
           
        {
            tapGestures = new SortedList<long, TapGesture>();
            touchGestures = new SortedList<long, TouchGesture>();
            slideGestures = new SortedList<long, SlideGesture>();
            dragGestures = new SortedList<long, DragGesture>();
            holdGestures = new SortedList<long, HoldGesture>();

            complexGestures = new List<ComplexGesture>();

            Gesture.contactManager = this;
            game.Components.Add(this);
            game.Services.AddService(typeof(TUIOContactManager), this); 
        }

        public double getDistance(Vector3 a, Vector3 b)
        {
            return Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2));
        }

        /// <summary>
        /// Adds a TouchGesture on a ContactAdded event.
        /// </summary>
        /// <param name="sender">Event sender</param>
        /// <param name="cea">Args for contact</param>
        public void ContactAdded(TuioCursor contact)
        {
            TouchGesture touch = new TouchGesture(contact);
            touchGestures.Add(touch.Id, touch);
            if(!RunCreators())
                touch.Start();
        }

        /// <summary>
        /// Fired when a contact is changed somehow. This could be a translation, rotation,
        /// or other state change. This will run through each basic gesture and call the update
        /// function.
        /// </summary>
        /// <param name="contact"></param>
        public void ContactChanged(TuioCursor contact)
        {
            long ID = contact.getSessionID();
            TouchGesture touch;
            DragGesture drag;
            SlideGesture slide;

            // If a touch changes, then it's turning into a slide or a drag.
            if (touchGestures.TryGetValue(ID, out touch))
            {
                touch.UpdateCurrentPosition(contact);
                gestureCheck_DragOrSlide(touch);
            }

            // If a drag changes, 
            else if (dragGestures.TryGetValue(ID, out drag))
            {
                drag.UpdateCurrentPosition(contact);
                if (drag.speed.Z == 1)
                {
                    slide = new SlideGesture(drag);
                    slideGestures.Add(slide.Id, slide);
                    destroyDrag(drag);
                    if (!RunCreators())
                        slide.Start();
                }
            }

            else if (slideGestures.TryGetValue(ID, out slide))
            {
                slide.UpdateCurrentPosition(contact);
                if (slide.speed.Z == 0)
                {
                    drag = new DragGesture(slide);
                    dragGestures.Add(drag.Id, drag);
                    destroySlide(slide);
                    if (!RunCreators())
                        drag.Start();
                }
            }
        }

        /// <summary>
        /// Informs the ContactManager that a Contact has been removed
        /// </summary>
        /// <param name="sender">Event sender</param>
        /// <param name="cea">Args for contact</param>
        public void ContactRemoved(TuioCursor contact)
        {
            TouchGesture touch;
            DragGesture drag;
            SlideGesture slide;
            HoldGesture hold;

            // Add a new Tap gesture if the touch is removed.
            if (touchGestures.TryGetValue(contact.getSessionID(), out touch))
            {
                if (touch.CanTap && DateTime.Now - touch.startTime < TapGesture.MAX_TAP_LENGTH)
                {
                    TapGesture tap = new TapGesture(touch);
                    tapGestures.Add(tap.Id, tap);
                    if (!RunCreators())
                        tap.Start();
                }
                destroyTouch(touch);
            }

            // Kill the slide upon removal.
            else if (slideGestures.TryGetValue(contact.getSessionID(), out slide))
                destroySlide(slide);

            // Kill the drag upon removal.
            else if (dragGestures.TryGetValue(contact.getSessionID(), out drag))
                destroyDrag(drag);

            else if (holdGestures.TryGetValue(contact.getSessionID(), out hold))
                destroyHold(hold);
        }

        /// <param name="sender">Event sender</param>
        /// <param name="cea">Args for contact</param>
        public void ContactTapGesture(TuioCursor contact)
        {
            TapGesture tap = new TapGesture(contact);
            tapGestures.Add(tap.Id, tap);
            if(!RunCreators())
                tap.Start();
        }

        public void destroyTouch(TouchGesture touch)
        {
            touch.Destroy();
            ComplexGesture parent = touch.Parent;
            if (parent == null)
                touch.OnDeath();
            else if (!parent.Destroyed())
            {
                parent.Destroy();
                parent.OnDeath();
                complexGestures.Remove(parent);
            }
            touchGestures.Remove(touch.Id);
        }

        public void destroySlide(SlideGesture slide)
        {
            slide.Destroy();
            ComplexGesture parent = slide.Parent;
            if (parent == null)
                slide.OnDeath();
            else if(!parent.Destroyed())
            {
                parent.Destroy();
                parent.OnDeath();
                complexGestures.Remove(parent);
            }
            slideGestures.Remove(slide.Id);
        }

        public void destroyDrag(DragGesture drag)
        {
            drag.Destroy();
            ComplexGesture parent = drag.Parent;
            if (parent == null)
                drag.OnDeath();
            else if (!parent.Destroyed())
            {
                parent.Destroy();
                parent.OnDeath();
                complexGestures.Remove(parent);
            }
            dragGestures.Remove(drag.Id);
        }

        public void destroyHold(HoldGesture hold)
        {
            hold.Destroy();
            ComplexGesture parent = hold.Parent;
            if (parent == null)
                hold.OnDeath();
            else if (!parent.Destroyed())
            {
                parent.Destroy();
                parent.OnDeath();
                complexGestures.Remove(parent);
            }
            holdGestures.Remove(hold.Id);
        }

        // This assumes the parent is a DoubleTap, so it's a little different.
        public void destroyTap(TapGesture tap)
        {
            tap.Destroy();
            ComplexGesture parent = tap.Parent;
            if (parent == null)
                tap.OnDeath();
            else if(parent.Destroyed())
            {
                parent.OnDeath();
                complexGestures.Remove(parent);
            }
            tapGestures.Remove(tap.Id);
        }

        public bool gestureCheck_DoubleTap()
        {
            TapGesture tap, tap2;
            for (int i = 0; i < TapGestures.Count; i++)
            {
                tap = TapGestures[i];
                for (int j = i + 1; j < TapGestures.Count; j++)
                {
                    tap2 = TapGestures[j];
                    double dist = Vector3.Distance(tap.startPosition, tap2.startPosition);
                    if (tap.Parent == null && dist < DoubleTapGesture.DOUBLE_TAP_RANGE)
                    {
                        DoubleTapGesture doubleTap = new DoubleTapGesture(tap, tap2);
                        complexGestures.Add(doubleTap);
                        doubleTap.Start();
                        return true;
                    }
                }
            }
            return false;
        }

        public bool gestureCheck_Zoom()
        {
            IList<DragGesture> drags = dragGestures.Values;
            DragGesture first, second;

            for (int i = 0; i < drags.Count; i++)
                if ((first = drags[i]).Parent == null)
                    for (int j = i + 1; j < drags.Count; j++)
                        if ((second = drags[j]).Parent == null)
                        {
                            ZoomGesture zoom = new ZoomGesture(first, second);
                            complexGestures.Add(zoom);
                            zoom.Start();
                            return true;
                        }
            return false;
        }

        public bool gestureCheck_HoldSlide()
        {
            HoldGesture hold;
            DragGesture drag;
            for (int i = 0; i < HoldGestures.Count; i++)
                if ((hold = HoldGestures[i]).Parent == null)
                    for (int j = 0; j < DragGestures.Count; j++)
                        if ((drag = DragGestures[j]).Parent == null &&
                            drag.startTime > hold.startTime)
                        {
                            HoldSlideGesture holdSlide = new HoldSlideGesture(hold, drag);
                            complexGestures.Add(holdSlide);
                            holdSlide.Start();
                            return true;
                        }

            SlideGesture slide;
            for (int i = 0; i < DragGestures.Count; i++)
                if ((drag = DragGestures[i]).Parent == null && drag.Id == 0)
                    for (int j = 0; j < SlideGestures.Count; j++)
                        if ((slide = SlideGestures[j]).Parent == null && slide.Id == 1)
                        {
                            HoldSlideGesture holdSlide = new HoldSlideGesture(drag, slide);
                            complexGestures.Add(holdSlide);
                            holdSlide.Start();
                            return true;
                        }
            return false;
        }

        public void gestureCheck_DragOrSlide(TouchGesture touch)
        {
            if (touch.Parent == null)
                if (getDistance(touch.currentPosition, touch.startPosition) > .01)
                {
                    //make a drag, then run complex creators on the drag. if there aren't any, go ahead and start().
                    if (touch.speed.Z == 0)
                    {
                        DragGesture drag = new DragGesture(touch);
                        dragGestures.Add(drag.Id, drag);
                        destroyTouch(touch);

                        if(!RunCreators())
                            drag.Start();
                    }
                    else if (touch.speed.Z == 1)
                    {
                        SlideGesture slide = new SlideGesture(touch);
                        slideGestures.Add(slide.Id, slide);
                        destroyTouch(touch);

                        if(!RunCreators())
                            slide.Start();
                    }
                }
        }


        public void gestureCheck_Hold(TouchGesture touch)
        {
            if (!touch.Destroyed() && touch.Parent == null && touchGestures.ContainsKey(touch.Id))
            {
                HoldGesture hold = new HoldGesture(touch);
                holdGestures.Add(hold.Id, hold);
                destroyTouch(touch);
                hold.Start();
            }
        }

        public bool RunCreators()
        {
            return (
                gestureCheck_DoubleTap() ||
                gestureCheck_HoldSlide() ||
                gestureCheck_Zoom()
            );
        }

        /// <summary>
        /// Debugging method, in case a gesture remains when it shouldn't.
        /// </summary>
        public void KillAllGestures()
        {
            complexGestures.Clear();
            touchGestures.Clear();
            tapGestures.Clear();
            slideGestures.Clear();
            dragGestures.Clear();
        }
    }
}